package com.gitee.wsl.jvm.security.argon2

import java.lang.Long.rotateRight
import org.bouncycastle.crypto.Digest
import org.bouncycastle.crypto.digests.Blake2bDigest
import org.bouncycastle.crypto.params.Argon2Parameters
import org.bouncycastle.util.Pack.intToLittleEndian
import org.bouncycastle.util.Pack.littleEndianToLong
import org.bouncycastle.util.Pack.longToLittleEndian

class Argon2BytesGenerator(private var parameters: Argon2Parameters) {
   
    private var memory: Array<Block?>? = null
    private var segmentLength = 0
    private var laneLength = 0
     init {
        if (parameters.lanes < 1) {
            throw IllegalStateException("lanes must be greater than 1")
        } else if (parameters.lanes > 16777216) {
            throw IllegalStateException("lanes must be less than 16777216")
        } else if (parameters.memory< 2 * parameters.lanes) {
            throw IllegalStateException("memory is less than: " + 2 * parameters.lanes + " expected " + 2 * parameters.lanes)
        } else check(parameters.iterations >= 1) { "iterations is less than: 1" }
        doInit(parameters)
    }

    fun generateBytes(var1: CharArray, var2: ByteArray): Int {
        return this.generateBytes(parameters.charToByteConverter.convert(var1), var2)
    }

    fun generateBytes(var1: CharArray, var2: ByteArray, var3: Int, var4: Int): Int {
        return this.generateBytes(
            parameters.charToByteConverter.convert(var1),
            var2,
            var3,
            var4
        )
    }

    fun generateBytes(var1: ByteArray, var2: ByteArray): Int {
        return this.generateBytes(var1, var2, 0, var2.size)
    }

    fun generateBytes(var1: ByteArray, var2: ByteArray, var3: Int, var4: Int): Int {
        return if (var4 < 4) {
            throw IllegalStateException("output length less than 4")
        } else {
            val var5 = ByteArray(1024)
            initialize(var5, var1, var4)
            fillMemoryBlocks()
            digest(var5, var2, var3, var4)
            reset()
            var4
        }
    }

    private fun reset() {
        if (null != memory) {
            for (var1 in memory!!.indices) {
                val var2 = memory!![var1]
                var2?.clear()
            }
        }
    }

    private fun doInit(var1: Argon2Parameters) {
        var var2: Int = var1.memory
        if (var2 < 8 * var1.lanes) {
            var2 = 8 * var1.lanes
        }
        segmentLength = var2 / (var1.lanes * 4)
        laneLength = segmentLength * 4
        var2 = segmentLength * var1.lanes * 4
        initMemory(var2)
    }

    private fun initMemory(var1: Int) {
        memory = arrayOfNulls(var1)
        for (var2 in memory!!.indices) {
            memory!![var2] = Block()
        }
    }

    private fun fillMemoryBlocks() {
        val var1 = FillBlock()
        val var2 = Position()
        for (var3 in 0 until parameters.iterations) {
            var2.pass = var3
            for (var4 in 0..3) {
                var2.slice = var4
                for (var5 in 0 until parameters.lanes) {
                    var2.lane = var5
                    fillSegment(var1, var2)
                }
            }
        }
    }

    private fun fillSegment(var1: FillBlock, var2: Position) {
        var var3: Block? = null
        var var4: Block? = null
        val var5 = isDataIndependentAddressing(var2)
        val var6 = getStartingIndex(var2)
        var var7 = var2.lane * laneLength + var2.slice * segmentLength + var6
        var var8 = getPrevOffset(var7)
        if (var5) {
            var3 = var1.addressBlock.clear()
            var4 = var1.inputBlock.clear()
            initAddressBlocks(var1, var2, var4, var3)
        }
        val var9 = isWithXor(var2)
        for (var10 in var6 until segmentLength) {
            val var11 = getPseudoRandom(var1, var10, var3, var4, var8, var5)
            val var13 = getRefLane(var2, var11)
            val var14 = getRefColumn(var2, var10, var11, var13 == var2.lane)
            val var15 = memory!![var8]
            val var16 = memory!![laneLength * var13 + var14]
            val var17 = memory!![var7]
            if (var9) {
                var1.fillBlockWithXor(var15, var16, var17)
            } else {
                var1.fillBlock(var15, var16, var17)
            }
            var8 = var7++
        }
    }

    private fun isDataIndependentAddressing(var1: Position): Boolean {
        return parameters.type== 1 || parameters.type == 2 && var1.pass == 0 && var1.slice < 2
    }

    private fun initAddressBlocks(var1: FillBlock, var2: Position, var3: Block?, var4: Block?) {
        var3!!.v[0] = intToLong(var2.pass)
        var3.v[1] = intToLong(var2.lane)
        var3.v[2] = intToLong(var2.slice)
        var3.v[3] = intToLong(memory!!.size)
        var3.v[4] = intToLong(parameters.iterations)
        var3.v[5] = intToLong(parameters.type)
        if (var2.pass == 0 && var2.slice == 0) {
            nextAddresses(var1, var3, var4)
        }
    }

    private fun isWithXor(var1: Position): Boolean {
        return var1.pass != 0 && parameters.version != 16
    }

    private fun getPrevOffset(var1: Int): Int {
        return if (var1 % laneLength == 0) var1 + laneLength - 1 else var1 - 1
    }

    private fun nextAddresses(var1: FillBlock, var2: Block?, var3: Block?) {
        val var10002 = var2!!.v[6]++.toInt()
        var1.fillBlock(var2, var3)
        var1.fillBlock(var3, var3)
    }

    private fun getPseudoRandom(
        var1: FillBlock,
        var2: Int,
        var3: Block?,
        var4: Block?,
        var5: Int,
        var6: Boolean
    ): Long {
        return if (var6) {
            val var7 = var2 % 128
            if (var7 == 0) {
                nextAddresses(var1, var4, var3)
            }
            var3!!.v[var7]
        } else {
            memory!![var5]!!.v[0]
        }
    }

    private fun getRefLane(var1: Position, var2: Long): Int {
        var var4: Int = ((var2 ushr 32) % parameters.lanes.toLong()).toInt()
        if (var1.pass == 0 && var1.slice == 0) {
            var4 = var1.lane
        }
        return var4
    }

    private fun getRefColumn(var1: Position, var2: Int, var3: Long, var5: Boolean): Int {
        val var6: Int
        val var7: Int
        if (var1.pass == 0) {
            var7 = 0
            var6 = if (var5) {
                var1.slice * segmentLength + var2 - 1
            } else {
                var1.slice * segmentLength + if (var2 == 0) -1 else 0
            }
        } else {
            var7 = (var1.slice + 1) * segmentLength % laneLength
            var6 = if (var5) {
                laneLength - segmentLength + var2 - 1
            } else {
                laneLength - segmentLength + if (var2 == 0) -1 else 0
            }
        }
        var var8 = var3 and 4294967295L
        var8 = var8 * var8 ushr 32
        var8 = (var6 - 1).toLong() - (var6.toLong() * var8 ushr 32)
        return (var7.toLong() + var8).toInt() % laneLength
    }

    private fun digest(var1: ByteArray, var2: ByteArray, var3: Int, var4: Int) {
        val var5 = memory!![laneLength - 1]
        for (var6 in 1 until parameters.lanes) {
            val var7 = var6 * laneLength + (laneLength - 1)
            var5!!.xorWith(memory!![var7])
        }
        var5!!.toBytes(var1)
        hash(var1, var2, var3, var4)
    }

    private fun hash(var1: ByteArray, var2: ByteArray, var3: Int, var4: Int) {
        val var5 = ByteArray(4)
        intToLittleEndian(var4, var5, 0)
        val var6: Byte = 64
        var var7: Blake2bDigest
        if (var4 <= var6) {
            var7 = Blake2bDigest(var4 * 8)
            var7.update(var5, 0, var5.size)
            var7.update(var1, 0, var1.size)
            var7.doFinal(var2, var3)
        } else {
            var7 = Blake2bDigest(var6 * 8)
            val var8 = ByteArray(var6.toInt())
            var7.update(var5, 0, var5.size)
            var7.update(var1, 0, var1.size)
            var7.doFinal(var8, 0)
            val var9 = var6 / 2
            System.arraycopy(var8, 0, var2, var3, var9)
            var var10 = var3 + var9
            val var11 = (var4 + 31) / 32 - 2
            var var12: Int
            var12 = 2
            while (var12 <= var11) {
                var7.update(var8, 0, var8.size)
                var7.doFinal(var8, 0)
                System.arraycopy(var8, 0, var2, var10, var9)
                ++var12
                var10 += var9
            }
            var12 = var4 - 32 * var11
            var7 = Blake2bDigest(var12 * 8)
            var7.update(var8, 0, var8.size)
            var7.doFinal(var2, var10)
        }
    }

    private fun initialize(var1: ByteArray, var2: ByteArray?, var3: Int) {
        val var4 = Blake2bDigest(512)
        val var5 = intArrayOf(
            parameters.lanes,
            var3,
            parameters.memory,
            parameters.iterations,
            parameters.getVersion(),
            parameters.type
        )
        intToLittleEndian(var5, var1, 0)
        var4.update(var1, 0, var5.size * 4)
        addByteString(var1, var4, var2)
        addByteString(var1, var4, parameters.getSalt())
        addByteString(var1, var4, parameters.getSecret())
        addByteString(var1, var4, parameters.getAdditional())
        val var6 = ByteArray(72)
        var4.doFinal(var6, 0)
        fillFirstBlocks(var1, var6)
    }

    private fun fillFirstBlocks(var1: ByteArray, var2: ByteArray) {
        val var3 = ByteArray(72)
        System.arraycopy(var2, 0, var3, 0, 64)
        var3[64] = 1
        for (var4 in 0 until parameters.lanes) {
            intToLittleEndian(var4, var2, 68)
            intToLittleEndian(var4, var3, 68)
            hash(var2, var1, 0, 1024)
            memory!![var4 * laneLength + 0]!!.fromBytes(var1)
            hash(var3, var1, 0, 1024)
            memory!![var4 * laneLength + 1]!!.fromBytes(var1)
        }
    }

    private fun intToLong(var1: Int): Long {
        return var1.toLong() and 4294967295L
    }

    private class Block() {
        val v: LongArray = LongArray(128)

        fun fromBytes(var1: ByteArray) {
            if (var1.size < 1024) {
                throw IllegalArgumentException("input shorter than blocksize")
            } else {
                littleEndianToLong(var1, 0, v)
            }
        }

        fun toBytes(var1: ByteArray) {
            if (var1.size < 1024) {
                throw IllegalArgumentException("output shorter than blocksize")
            } else {
                longToLittleEndian(v, var1, 0)
            }
        }

        fun copyBlock(var1: Block?) {
            System.arraycopy(var1!!.v, 0, v, 0, 128)
        }

        fun xor(var1: Block?, var2: Block?) {
            val var3 = v
            val var4 = var1!!.v
            val var5 = var2!!.v
            for (var6 in 0..127) {
                var3[var6] = var4[var6] xor var5[var6]
            }
        }

        fun xorWith(var1: Block?) {
            val var2 = v
            val var3 = var1!!.v
            for (var4 in 0..127) {
                var2[var4] = var2[var4] xor var3[var4]
            }
        }

        fun xorWith(var1: Block, var2: Block) {
            val var3 = v
            val var4 = var1.v
            val var5 = var2.v
            for (var6 in 0..127) {
                var3[var6] = var3[var6] xor (var4[var6] xor var5[var6])
            }
        }

        fun clear(): Block {
            //fill(v, 0L)
            v.fill(0L)
            return this
        }

        companion object {
            private const val SIZE = 128
        }
    }

    private class FillBlock() {
        var R: Block = Block()
        var Z: Block = Block()
        var addressBlock: Block = Block()
        var inputBlock: Block = Block()

        private fun applyBlake() {
            var var2: Int
            var var1: Int = 0
            while (var1 < 8) {
                var2 = 16 * var1
                roundFunction(
                    Z,
                    var2,
                    var2 + 1,
                    var2 + 2,
                    var2 + 3,
                    var2 + 4,
                    var2 + 5,
                    var2 + 6,
                    var2 + 7,
                    var2 + 8,
                    var2 + 9,
                    var2 + 10,
                    var2 + 11,
                    var2 + 12,
                    var2 + 13,
                    var2 + 14,
                    var2 + 15
                )
                ++var1
            }
            var1 = 0
            while (var1 < 8) {
                var2 = 2 * var1
                roundFunction(
                    Z,
                    var2,
                    var2 + 1,
                    var2 + 16,
                    var2 + 17,
                    var2 + 32,
                    var2 + 33,
                    var2 + 48,
                    var2 + 49,
                    var2 + 64,
                    var2 + 65,
                    var2 + 80,
                    var2 + 81,
                    var2 + 96,
                    var2 + 97,
                    var2 + 112,
                    var2 + 113
                )
                ++var1
            }
        }

        fun fillBlock(var1: Block?, var2: Block?) {
            Z.copyBlock(var1)
            applyBlake()
            var2!!.xor(var1, Z)
        }

        fun fillBlock(var1: Block?, var2: Block?, var3: Block?) {
            R.xor(var1, var2)
            Z.copyBlock(R)
            applyBlake()
            var3!!.xor(R, Z)
        }

        fun fillBlockWithXor(var1: Block?, var2: Block?, var3: Block?) {
            R.xor(var1, var2)
            Z.copyBlock(R)
            applyBlake()
            var3!!.xorWith(R, Z)
        }
    }

    private class Position() {
        var pass = 0
        var lane = 0
        var slice = 0
    }

    companion object {
        private const val ARGON2_BLOCK_SIZE = 1024
        private const val ARGON2_QWORDS_IN_BLOCK = 128
        private const val ARGON2_ADDRESSES_IN_BLOCK = 128
        private const val ARGON2_PREHASH_DIGEST_LENGTH = 64
        private const val ARGON2_PREHASH_SEED_LENGTH = 72
        private const val ARGON2_SYNC_POINTS = 4
        private const val MIN_PARALLELISM = 1
        private const val MAX_PARALLELISM = 16777216
        private const val MIN_OUTLEN = 4
        private const val MIN_ITERATIONS = 1
        private const val M32L = 4294967295L
        private val ZERO_BYTES = ByteArray(4)
        private fun getStartingIndex(var0: Position): Int {
            return if (var0.pass == 0 && var0.slice == 0) 2 else 0
        }

        private fun roundFunction(
            var0: Block,
            var1: Int,
            var2: Int,
            var3: Int,
            var4: Int,
            var5: Int,
            var6: Int,
            var7: Int,
            var8: Int,
            var9: Int,
            var10: Int,
            var11: Int,
            var12: Int,
            var13: Int,
            var14: Int,
            var15: Int,
            var16: Int
        ) {
            val var17 = var0.v
            F(var17, var1, var5, var9, var13)
            F(var17, var2, var6, var10, var14)
            F(var17, var3, var7, var11, var15)
            F(var17, var4, var8, var12, var16)
            F(var17, var1, var6, var11, var16)
            F(var17, var2, var7, var12, var13)
            F(var17, var3, var8, var9, var14)
            F(var17, var4, var5, var10, var15)
        }

        private fun F(var0: LongArray, var1: Int, var2: Int, var3: Int, var4: Int) {
            quarterRound(var0, var1, var2, var4, 32)
            quarterRound(var0, var3, var4, var2, 24)
            quarterRound(var0, var1, var2, var4, 16)
            quarterRound(var0, var3, var4, var2, 63)
        }

        private fun quarterRound(var0: LongArray, var1: Int, var2: Int, var3: Int, var4: Int) {
            var var5 = var0[var1]
            val var7 = var0[var2]
            var var9 = var0[var3]
            var5 += var7 + 2L * (var5 and 4294967295L) * (var7 and 4294967295L)
            var9 = rotateRight(var9 xor var5, var4)
            var0[var1] = var5
            var0[var3] = var9
        }

        private fun addByteString(
            var0: ByteArray,
            var1: Digest,
            var2: ByteArray?
        ) {
            if (null == var2) {
                var1.update(ZERO_BYTES, 0, 4)
            } else {
                intToLittleEndian(var2.size, var0, 0)
                var1.update(var0, 0, 4)
                var1.update(var2, 0, var2.size)
            }
        }
    }
}

